package midend.IR_Optimizer;

import midend.IR_Instruction.*;
import midend.IR_Module;
import midend.IR_Type.*;
import midend.IR_Value.IR_BaseBlock;
import midend.IR_Value.IR_FuncBlock;
import midend.IR_Value.IR_Local_Value;
import midend.IR_Value.IR_Value;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;

import static midend.IR_Builder.IR_Atendwithprenext;
import static midend.IR_Builder.IR_deletewithprenext;

public class Remove_phi {
    private IR_Module ir_module;
    private IR_Type i32Type;
    private IR_Type i1Type;
    private IR_Type floatType;
    private IR_Type voidType;

    public Remove_phi(IR_Module module) {
        this.ir_module = module;
        this.i32Type = IR_Int32.IR_Int32();
        this.i1Type = IR_Int1.IR_Int1();
        this.floatType = IR_Float.IR_Float();
        this.voidType = IR_Void.IR_Void();
        removeUnUsePhi();
    }

    private void removeUnUsePhi(){
        for (IR_FuncBlock function : ir_module.getFuncBlocks()) {
            removeUseLessPhi(function);
        }
    }

    private void replaceUse(IR_FuncBlock function,IR_Value pre,IR_Value now){
        for(IR_BaseBlock baseBlock:function.get_bb_List()){
            for(Instruction instruction:baseBlock.get_Instruction_List()) {
                //if(instruction.get_usevaluelist().contains(pre)){
                if(instruction instanceof Phi_Instruction phiInstruction && phiInstruction.get_op_index(0).equals(pre)){
                    continue;
                }
                for (int i = 0; i < instruction.get_op().size(); i++) {
                    if (instruction.get_op_index(i).equals(pre)) {
                        instruction.set_Op(i, now);
                    }
                }
                //}
            }
        }
    }

    public void run() {
        ir_module.buildCFG();
        for (IR_FuncBlock function : ir_module.getFuncBlocks()) {
            phi2copy(function);
            copy2Move(function);
        }
    }
    public void removeUseLessPhi(IR_FuncBlock function) {
        boolean change = true;
        while (change) {
            change = false;
            for (IR_BaseBlock block : function.get_bb_List()) {
                ArrayList<Instruction> delete = new ArrayList<>();
                for (int count0 = 0; count0 < block.get_Instruction_List().size(); count0++) {
                    Instruction instruction = block.get_Instruction_List().get(count0);
                    boolean canBedelete = true;
                    if (instruction instanceof Phi_Instruction) {
                        if (((Phi_Instruction) instruction).getValueList().isEmpty()) {
                            //特别判断如果此时 phi 后面为 空 必须删除
                            delete.add(instruction);
                            change = true;
                        }
                        else if(((Phi_Instruction) instruction).getValueList().size() == 1){
                            continue;
                        }
                        else{
                            //获取下一条指令
                            Instruction next = block.get_Instruction_List().get(count0 + 1);

                            IR_Value first = ((Phi_Instruction) instruction).getValue(0);

                            for (int count = 1; count < ((Phi_Instruction) instruction).getValueList().size(); count++) {
                                //如果 phi 所有可能转移的块里面存在一个不一样的结果就不可以删除
                                if (!((Phi_Instruction) instruction).getValue(count).equals(first)) {
                                    canBedelete = false;
                                    break;
                                }
                            }

                            if (canBedelete) {
                                //first 就是需要替换变成的
                                IR_Value prevalue = instruction.get_op_index(0);
                                replaceUse(function,prevalue,first);
                                //instruction.get_op_index(0).replaceUse(first);
                                delete.add(instruction);
                                change = true;
                                continue;
                            }
                            //分析下一条 指令
                            if (next != null && next instanceof Phi_Instruction) {
                                // 如果上下两条相同 删除
                                if (((Phi_Instruction) next).getValueList().size() != ((Phi_Instruction) instruction).getValueList().size()) {
                                    continue;
                                }
                                boolean isEqual = true;
                                for (int count1 = 0; count1 < ((Phi_Instruction) instruction).getValueList().size(); count1++) {
                                    //分析上下两条指令 是不是完全相同的
                                    IR_Value before = ((Phi_Instruction) instruction).getValue(count1);
                                    IR_Value after = ((Phi_Instruction) next).getValue(count1);
                                    if (!before.equals(after) || !((Phi_Instruction) instruction).getPreBlock(count1).equals(((Phi_Instruction) next).getPreBlock(count1))) {
                                        isEqual = false;
                                        break;
                                    }
                                }
                                if (isEqual) {
                                    delete.add(instruction);
                                    change = true;
                                }
                            }
                        }
                    }
                }
                for (Instruction instruction : delete) {
                    instruction.remove();
                }
            }
        }
    }

    public void phi2copy(IR_FuncBlock function) {
        ArrayList<IR_BaseBlock> bbList = new ArrayList<>(function.get_bb_List());
        for (IR_BaseBlock block : bbList) {
            if (!(block.get_Instruction_List().getFirst() instanceof Phi_Instruction)) {
                continue;
            }
            ArrayList<PCopy_Instruction> pCopyInstrs = new ArrayList<>();
            ArrayList<IR_BaseBlock> preBlocks = new ArrayList<>(block.getPreBlock());

            for (int count = 0; count < preBlocks.size(); count++) {
                IR_BaseBlock preBlock = preBlocks.get(count);
                //后续的指令信息和 所属于的baseblock都是后续手动设定的
                PCopy_Instruction pCopy = new PCopy_Instruction(Arrays.asList(null,null));
                pCopy.setPastbb(preBlock);
                pCopyInstrs.add(pCopy);
                //获取 前驱块的后继块
                if (preBlock.getNextBlock().size() == 1) {
                    //不需要进行选择
                    LinkedList<Instruction> instructions = preBlock.get_Instruction_List();
                    instructions.add(instructions.size() - 1, pCopy);
                    pCopy.set_bb(preBlock);
                } else {
                    //添加一个中间块进行处理 phi 指令
                    IR_BaseBlock midBlock = new IR_BaseBlock("midblock");
                    function.addbb(midBlock);
                    midBlock.add_InstructionList(pCopy);
                    pCopy.set_bb(midBlock);
                    //修改跳转指令
                    Branch_Instruction brInstr = (Branch_Instruction) preBlock.get_Instruction_List().getLast();
                    IR_BaseBlock ifTrueBlock = brInstr.get_block1();
                    IR_BaseBlock ifFalseBlock = brInstr.get_block2();
                    if (block.equals(ifTrueBlock)) {
                        brInstr.setBlock1(midBlock);
                        Branch_Instruction midBr = new Branch_Instruction(Arrays.asList(block),midBlock);
                        midBlock.add_InstructionList(midBr);
                    } else {
                        brInstr.setBlock2(midBlock);
                        Branch_Instruction midBr = new Branch_Instruction(Arrays.asList(block),midBlock);
                        midBlock.add_InstructionList(midBr);
                    }
                    //维持前驱后继关系
                    IR_deletewithprenext(preBlock,block);
                    IR_Atendwithprenext(preBlock,midBlock);
                    IR_Atendwithprenext(midBlock,block);
                }
            }

            //已经完成了插入pcopy，删除phi
            ArrayList<Instruction> deletePhi = new ArrayList<>();
            for (Instruction instruction : block.get_Instruction_List()) {
                if (instruction instanceof Phi_Instruction) {
                    Phi_Instruction phiInstr = (Phi_Instruction) instruction;
                    deletePhi.add(phiInstr);
//                    System.out.println(phiInstr);
//                    System.out.println(phiInstr.getValueList());
//                    System.out.println(phiInstr.get_ComingValues().keySet());
//                    System.out.println(phiInstr.get_bb().getPreBlock());
//                    System.out.println(pCopyInstrs.size());
                    for (int count = 0; count < phiInstr.getValueList().size(); count++) {
                        PCopy_Instruction pCopyInstr = pCopyInstrs.get(count);
                        IR_Value now = phiInstr.get_ComingValues().get(pCopyInstr.pastbb());
                        pCopyInstr.addCopy(Arrays.asList(phiInstr.get_op_index(0), now));
                    }
                }
            }
            for (Instruction instruction : deletePhi) {
                instruction.remove();
            }
        }
    }

    public void copy2Move(IR_FuncBlock function) {
        for (IR_BaseBlock block : function.get_bb_List()) {
            LinkedList<Instruction> instructions = block.get_Instruction_List();
            if (instructions.size() > 1 && instructions.get(instructions.size() - 2) instanceof PCopy_Instruction) {
                PCopy_Instruction pCopyInstr = (PCopy_Instruction) instructions.get(instructions.size() - 2);
                pCopyInstr.remove();

                LinkedList<Move_Instruction> moveInstrs = getMove(pCopyInstr);
                for (Move_Instruction moveInstr : moveInstrs) {
                    if (moveInstr.getSrc().get_Type() == i32Type && moveInstr.getDst().get_Type() == floatType) {
                        //res  = 0 origin  = 1
                        IR_Value value = new IR_Local_Value("tmp", floatType);
                        Type_Trans_Instruction conversionInstr = new Type_Trans_Instruction(Arrays.asList(value,moveInstr.getSrc()),moveInstr.get_bb(),IR_Const.IntToFloat);
                        instructions.add(instructions.size() - 1, conversionInstr);
                        //设定指令属于 的 bb
                        conversionInstr.set_bb(block);
                        moveInstr.setSrc(conversionInstr.res());
                    } else if (moveInstr.getSrc().get_Type() == floatType && moveInstr.getDst().get_Type() == i32Type) {
                        IR_Value value = new IR_Local_Value("tmp", i32Type);
                        Type_Trans_Instruction conversionInstr = new Type_Trans_Instruction(Arrays.asList(value,moveInstr.getSrc()),moveInstr.get_bb(),IR_Const.FloatToInt);
                        instructions.add(instructions.size() - 1, conversionInstr);
                        conversionInstr.set_bb(block);
                        moveInstr.setSrc(conversionInstr.res());
                    }
                    instructions.add(instructions.size() - 1, moveInstr);
                    moveInstr.set_bb(block);
                }
            }
        }
    }

    public LinkedList<Move_Instruction> getMove(PCopy_Instruction pCopyInstr) {
        ArrayList<IR_Value> src = pCopyInstr.getNow();
        ArrayList<IR_Value> dst = pCopyInstr.getBefore();

        LinkedList<Move_Instruction> moveInstrs = new LinkedList<>();
        for (int count = 0; count < src.size(); count++) {
            Move_Instruction moveInstr = new Move_Instruction(Arrays.asList(src.get(count), dst.get(count)),pCopyInstr.get_bb());
            moveInstrs.add(moveInstr);
        }
        ArrayList<Move_Instruction> midValue = new ArrayList<>();
        HashSet<IR_Value> visited = new HashSet<>(); //?

        //检查有无循环依赖 如 a = b  b = a
        for (int count = 0; count < moveInstrs.size(); count++) {
            IR_Value value = moveInstrs.get(count).getDst();
            if (!visited.contains(value)) {
                visited.add(value);
                boolean isLoopAssign = false;
                // 是否下一条 copy把 这个的 目标作为源
                for (int count1 = count + 1; count1 < moveInstrs.size(); count1++) {
                    if (moveInstrs.get(count1).getSrc().equals(value)) {
                        isLoopAssign = true;
                        break;
                    }
                }
                //存在循环冲突
                if (isLoopAssign) {
                    //存入一个临时变量里面
                    IR_Value midVal = new IR_Local_Value(value.get_ValueName() + "_temp",value.get_Type());
                    Move_Instruction moveInstr = new Move_Instruction(Arrays.asList(value,midVal),pCopyInstr.get_bb());
                    midValue.add(moveInstr);
                    for (Move_Instruction moveInstr1 : moveInstrs) {
                        if (moveInstr1.getSrc().equals(value)) {
                            moveInstr1.setSrc(midVal);
                        }
                    }
                }
            }
        }

        //TODO:move中的寄存器冲突
        for (Move_Instruction moveInstr : midValue) {
            moveInstrs.addFirst(moveInstr);
        }
        return moveInstrs;
    }
}
